package ru.ifmo.genetics.mutation.string;

import org.uncommons.maths.number.NumberGenerator;
import org.uncommons.maths.random.Probability;
import ru.ifmo.genetics.factory.string.DistributionBasedStringFactory;
import ru.ifmo.genetics.mutation.AbstractMutation;

import java.util.List;
import java.util.Random;

/**
 * @author Roman Kolganov
 *         03.12.11
 */
public class DistributionBasedComplexStringMutation extends AbstractMutation<String> {

    private final List<Character> alphabet;
    private final NumberGenerator<Integer> symbolNumberGenerator;
    private final NumberGenerator<Integer> maxLength;
    private final NumberGenerator<Probability> localMutationProbability;

    public DistributionBasedComplexStringMutation(NumberGenerator<Probability> mutationProbability, List<Character> alphabet,
                                                  NumberGenerator<Integer> symbolNumberGenerator, NumberGenerator<Integer> maxLength,
                                                  NumberGenerator<Probability> localMutationProbability) {
        super(mutationProbability);
        this.alphabet = alphabet;
        this.symbolNumberGenerator = symbolNumberGenerator;
        this.maxLength = maxLength;
        this.localMutationProbability = localMutationProbability;
    }

    @Override
    protected String mutate(String individual, Random rng) {
        if (rng.nextInt(2) == 1 && individual.length() < maxLength.nextValue()) {
            individual = insertSubword(individual, rng);
        } else {
            individual = deleteSubword(individual, rng);
        }
        String result = substituteLetters(individual, rng);
        if (result.length() == 0) {
            result = result + alphabet.get(symbolNumberGenerator.nextValue());
        }
        return result;
    }

    private String substituteLetters(String individual, Random rng) {
        char[] individualArray = individual.toCharArray();
        for (int i = 0; i < individualArray.length; i++) {
            if (localMutationProbability.nextValue().nextEvent(rng)) {
                char newChar = alphabet.get(rng.nextInt(alphabet.size() - 1));
                if (newChar == individualArray[i]) {
                    newChar = alphabet.get(alphabet.size() - 1);
                }
                individualArray[i] = newChar;
            }
        }
        return new String(individualArray);
    }

    private String insertSubword(final String individual, Random rng) {
        int cutPoint = rng.nextInt(individual.length() + 1);
        DistributionBasedStringFactory subwordFactory = new DistributionBasedStringFactory(
                new NumberGenerator<Integer>() {
                    public Integer nextValue() {
                        return maxLength.nextValue() - individual.length();
                    }
                },
                alphabet, symbolNumberGenerator);
        return individual.substring(0, cutPoint) + subwordFactory.generateRandomCandidate(rng) +
                individual.substring(cutPoint);
    }

    private String deleteSubword(String individual, Random rng) {
        int beginIndex = rng.nextInt(individual.length());
        int length = rng.nextInt(individual.length() - beginIndex) + 1;
        return individual.substring(0, beginIndex) + individual.substring(beginIndex + length);
    }

}
